package com.jumper.property.comparer.core.support;

import com.alibaba.fastjson.JSON;
import com.jumper.property.comparer.core.context.ActionContext;
import com.jumper.property.comparer.core.context.DataChangeContext;
import com.jumper.property.comparer.core.enums.LogType;
import com.jumper.property.comparer.core.exception.BizLogException;
import com.jumper.property.comparer.core.logger.DataLoggerImpl;
import com.jumper.property.comparer.core.pool.ThreadPool;
import com.jumper.property.comparer.core.result.Log;
import com.jumper.property.comparer.core.result.User;
import com.jumper.property.comparer.core.support.param.LogPageParam;
import com.jumper.property.comparer.core.support.param.Vars;
import com.jumper.property.comparer.core.support.result.LogEntity;
import com.jumper.property.comparer.core.support.result.LogEntityPage;
import com.jumper.property.comparer.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 操作日志管理器
 */
@Slf4j
public abstract class AbstractLogManager<T> implements LogManager<T>, OptDataQueryService<T> {

    protected final DataLogger logger = new DataLoggerImpl();
    protected final ThreadLocal<Object> beforeLocal = new InheritableThreadLocal<>();
    protected final ThreadLocal<Object> afterLocal = new InheritableThreadLocal<>();
    protected final LogRepository repository;

    public AbstractLogManager(LogRepository repository) {
        this.repository = repository;
    }

    @Override
    public <V> V submit(T optDataKey, String optName, Class<?> cls, User user, Function<InTransactionQuery, V> transactionFunc) {
        V res;
        try {
            doBefore();
            beforeQuerySet(optDataKey);
            res = transactionFunc.apply(
                    new LogInTransactionQuery<T>(optDataKey, this)
            );
            changeCommit(optDataKey, optName, cls, user);
        } finally {
            changeClear();
            doAfter();
        }
        return res;
    }

    @Override
    public void execute(T optDataKey, String optName, Class<?> cls, User user, Consumer<InTransactionQuery> transactionFunc) {
        submit(optDataKey, optName, cls, user, () -> {
            transactionFunc.accept(
                    new LogInTransactionQuery<T>(optDataKey, this)
            );
            return null;
        });
    }

    @Override
    @Deprecated
    public void execute(T optDataKey, String optName, Class<?> cls, User user, Runnable runnable) {
        submit(optDataKey, optName, cls, user, () -> {
            runnable.run();
            return null;
        });
    }

    @Override
    @Deprecated
    public <V> V submit(T optDataKey, String optName, Class<?> cls, User user, Callback<V> callable) {
        V res;
        try {
            doBefore();
            beforeQuerySet(optDataKey);
            res = callable.call();
            changeCommit(optDataKey, optName, cls, user);
        } finally {
            changeClear();
            doAfter();
        }
        return res;
    }

    @Override
    public void logAction(T optDataKey, String optName, String logValue, User user) {
        async(() -> {
            try {
                ActionContext context = new ActionContext(String.valueOf(optDataKey), optName, logValue, user);
                saveBusLogs(logger.log(context, LogType.Action));
            } catch (Exception e) {
                log.error("保存操作业务日志异常,optDataKey:{},optName:{},logValue:{},optUser:{}", optDataKey, optName, logValue, JSON.toJSONString(user), e);
            }
        });
    }

    @Override
    public void logAction(T optDataKey, String optName, String logValueTemplate, Vars vars, User user) {
        try {
            String logValue = replaceVars(logValueTemplate, vars);
            logAction(optDataKey, optName, logValue, user);
        } catch (Exception e) {
            log.error("保存操作业务日志Val渲染异常,optDataKey:{},optName:{},logValue:{}, vars:{}, optUser:{}", optDataKey, optName, logValueTemplate, JSON.toJSONString(vars), JSON.toJSONString(user), e);
        }
    }

    @Override
    public void logActionDirect(T optDataKey, String optName, String logValue, User user) {
        try {
            ActionContext context = new ActionContext(String.valueOf(optDataKey), optName, logValue, user);
            saveBusLogs(logger.log(context, LogType.Action));
        } catch (Exception e) {
            log.error("同步保存操作业务日志异常,optDataKey:{},optName:{},logValue:{},optUser:{}", optDataKey, optName, logValue, JSON.toJSONString(user), e);
            throw new BizLogException("同步保存操作业务日志异常:" + e.getMessage());
        }
    }

    @Override
    public void logActionDirect(T optDataKey, String optName, String logValueTemplate, Vars vars, User user) {
        try {
            String logValue = replaceVars(logValueTemplate, vars);
            logActionDirect(optDataKey, optName, logValue, user);
        } catch (Exception e) {
            log.error("同步保存操作业务日志Val渲染异常,optDataKey:{},optName:{},logValue:{}, vars:{}, optUser:{}", optDataKey, optName, logValueTemplate, JSON.toJSONString(vars), JSON.toJSONString(user), e);
            throw new BizLogException("同步保存操作业务日志Val渲染异常:" + e.getMessage());
        }
    }

    @Override
    public void logAction(Collection<T> optDataKeys, String optName, String logValue, User user) {
        if (Objects.nonNull(optDataKeys)) {
            optDataKeys.forEach(key -> logAction(key, optName, logValue, user));
        }
    }

    @Override
    public void logAction(Collection<T> optDataKeys, String optName, String logValueTemplate, Vars vars, User user) {
        if (Objects.nonNull(optDataKeys)) {
            optDataKeys.forEach(key -> logAction(key, optName, logValueTemplate, vars, user));
        }
    }

    @Deprecated
    @Override
    public void logAction(T optDataKey, String optName, User user) {
        logAction(optDataKey, optName, optName, user);
    }

    @Override
    public LogEntityPage getLogPage(LogPageParam param) {
        return repository.getLogsPage(getBusType(), param);
    }

    /**
     * 异步执行
     */
    protected void async(Runnable runnable) {
        ThreadPool.execute(() -> {
            try {
                doBefore();
                runnable.run();
            } finally {
                doAfter();
            }
        });
    }

    /**
     * 执行前操作
     */
    protected void doAfter() {
    }

    /**
     * 执行后操作
     */
    protected void doBefore() {
    }

    /**
     * 保存到数据库或者消息
     */
    protected void saveBusLogs(List<Log> logs) {
        List<LogEntity> busLogs = convertToBizLog(logs);
        flushToDB(busLogs);
    }

    /**
     * 保存日志到数据库
     */
    protected void flushToDB(List<LogEntity> busLogs) {
        repository.save(getBusType(), busLogs);
    }

    /**
     * 转Biz日志
     */
    protected List<LogEntity> convertToBizLog(List<Log> logs) {
        return logs.stream().map(busLog -> {
            LogEntity logDO = new LogEntity();
            logDO.setGmtCreated(LocalDateTime.now());
            logDO.setGmtModified(LocalDateTime.now());
            logDO.setAction(busLog.getAction());
            logDO.setDataKey(busLog.getDataKey() == null ? "" : busLog.getDataKey());
            logDO.setLogValue(busLog.getLogValue());
            User user = busLog.getUser();
            logDO.setOptUserId(user == null ? 0L : Long.parseLong(user.getOptUserId()));
            logDO.setOptUserName(user == null ? "" : user.getOptUserName());
            return logDO;
        }).collect(Collectors.toList());
    }

    /**
     * 清楚缓存
     */
    protected void changeClear() {
        beforeLocal.remove();
        afterLocal.remove();
    }

    /**
     * 数据变更之前
     */
    protected void beforeQuerySet(T optDataKey) {
        beforeLocal.set(getOptData(optDataKey));
    }

    /**
     * 数据变更之后
     */
    protected void afterQuerySet(T optDataKey) {
        afterLocal.set(getOptData(optDataKey));
    }

    /**
     * 数据变更提交
     */
    private void changeCommit(T optDataKey, String optName, Class<?> cls, User user) {
        try {
            Object before = beforeLocal.get();
            Object after = afterLocal.get();
            if (after == null) {
                log.info("After获取为空, 重新查询");
                after = getOptData(optDataKey);
            }
            logDataChange(optDataKey, optName, cls, before, after, user);
        } finally {
            changeClear();
        }
    }

    /**
     * 日志记录
     */
    private void logDataChange(T optDataKey, String optName, Class<?> cls, Object beforeData, Object afterData, User user) {
        async(() -> {
            try {
                DataChangeContext context = new DataChangeContext(String.valueOf(optDataKey), optName, cls, beforeData, afterData, user);
                saveBusLogs(logger.log(context, LogType.DataChange));
            } catch (Exception e) {
                log.error("保存操作业务变更日志异常,optDataKey:{},optName:{},optUser:{},cls:{},beforeData:{},afterData:{}",
                        optDataKey, optName, JSON.toJSONString(user), cls, JSON.toJSONString(beforeData), JSON.toJSONString(afterData), e);
            }
        });
    }

    /**
     * 替换变量
     */
    private String replaceVars(String logValueTemplate, Vars vars) {
        if (vars == null || StringUtils.isEmpty(logValueTemplate)) {
            return logValueTemplate;
        }
        for (String key : vars.keySet()) {
            logValueTemplate = logValueTemplate.replace("{{" + key + "}}", Optional.ofNullable(vars.get(key)).map(String::valueOf).orElse(""));
        }
        return logValueTemplate;
    }
}
